﻿using System;
using UnityEngine;

namespace Obi
{
	public struct ObiPathFrame
    {
        public enum Axis
        {
            X = 0,
            Y = 1,
            Z = 2
        }

        public static ObiPathFrame Identity => new ObiPathFrame(Vector3.zero, Vector3.forward, Vector3.up, Vector3.right, Color.white, 0);

        public Vector3 position;

        public Vector3 tangent;
        public Vector3 normal;
        public Vector3 binormal;

		public Vector4 color;
        public float thickness;
       
        public ObiPathFrame(Vector3 position, Vector3 tangent, Vector3 normal, Vector3 binormal, Vector4 color, float thickness){
			this.position = position;
			this.normal = normal;
			this.tangent = tangent;
            this.binormal = binormal;
			this.color = color;
            this.thickness = thickness;
        }

        public void Reset()
        {
            position = Vector3.zero;
            tangent = Vector3.forward;
            normal = Vector3.up;
            binormal = Vector3.right;
            color = Color.white;
            thickness = 0;
        }

		public static ObiPathFrame operator +(ObiPathFrame c1, ObiPathFrame c2) 
	    {
            return new ObiPathFrame(c1.position + c2.position,c1.tangent + c2.tangent,c1.normal + c2.normal,c1.binormal + c2.binormal,c1.color + c2.color, c1.thickness + c2.thickness);
	    }

		public static ObiPathFrame operator *(float f,ObiPathFrame c) 
	    {
            return new ObiPathFrame(c.position * f, c.tangent * f, c.normal * f, c.binormal * f,c.color * f, c.thickness * f);
	    }

        public static void WeightedSum(float w1, float w2, float w3, in ObiPathFrame c1, in ObiPathFrame c2, in ObiPathFrame c3, ref ObiPathFrame sum)
        {
            sum.position.x = c1.position.x * w1 + c2.position.x * w2 + c3.position.x * w3;
            sum.position.y = c1.position.y * w1 + c2.position.y * w2 + c3.position.y * w3;
            sum.position.z = c1.position.z * w1 + c2.position.z * w2 + c3.position.z * w3;

            sum.tangent.x = c1.tangent.x * w1 + c2.tangent.x * w2 + c3.tangent.x * w3;
            sum.tangent.y = c1.tangent.y * w1 + c2.tangent.y * w2 + c3.tangent.y * w3;
            sum.tangent.z = c1.tangent.z * w1 + c2.tangent.z * w2 + c3.tangent.z * w3;

            sum.normal.x = c1.normal.x * w1 + c2.normal.x * w2 + c3.normal.x * w3;
            sum.normal.y = c1.normal.y * w1 + c2.normal.y * w2 + c3.normal.y * w3;
            sum.normal.z = c1.normal.z * w1 + c2.normal.z * w2 + c3.normal.z * w3;

            sum.binormal.x = c1.binormal.x * w1 + c2.binormal.x * w2 + c3.binormal.x * w3;
            sum.binormal.y = c1.binormal.y * w1 + c2.binormal.y * w2 + c3.binormal.y * w3;
            sum.binormal.z = c1.binormal.z * w1 + c2.binormal.z * w2 + c3.binormal.z * w3;

            sum.color.x = c1.color.x * w1 + c2.color.x * w2 + c3.color.x * w3;
            sum.color.y = c1.color.y * w1 + c2.color.y * w2 + c3.color.y * w3;
            sum.color.z = c1.color.z * w1 + c2.color.z * w2 + c3.color.z * w3;
            sum.color.w = c1.color.w * w1 + c2.color.w * w2 + c3.color.w * w3;

            sum.thickness = c1.thickness * w1 + c2.thickness * w2 + c3.thickness * w3;
        }

        public void SetTwist(float twist)
        {
            Quaternion twistQ = Quaternion.AngleAxis(twist, tangent);
            normal = twistQ * normal;
            binormal = twistQ * binormal;
        }

        public void SetTwistAndTangent(float twist, Vector3 tangent)
        {
            this.tangent = tangent;
            normal = new Vector3(tangent.y, tangent.x, 0).normalized;
            binormal = Vector3.Cross(normal, tangent);

            Quaternion twistQ = Quaternion.AngleAxis(twist, tangent);
            normal = twistQ * normal;
            binormal = twistQ * binormal;
        }

        public void Transport(ObiPathFrame frame, float twist)
        {
            // Calculate delta rotation:
            Quaternion rotQ = Quaternion.FromToRotation(tangent, frame.tangent);
            Quaternion twistQ = Quaternion.AngleAxis(twist, frame.tangent);
            Quaternion finalQ = twistQ * rotQ;

            // Rotate previous frame axes to obtain the new ones:
            normal = finalQ * normal;
            binormal = finalQ * binormal;
            tangent = frame.tangent;
            position = frame.position;
            thickness = frame.thickness;
            color = frame.color;
        }

        public void Transport(Vector3 newPosition, Vector3 newTangent, float twist)
        {
            // Calculate delta rotation:
            Quaternion rotQ = Quaternion.FromToRotation(tangent, newTangent);
            Quaternion twistQ = Quaternion.AngleAxis(twist, newTangent);
            Quaternion finalQ = twistQ * rotQ;

            // Rotate previous frame axes to obtain the new ones:
            normal = finalQ * normal;
            binormal = finalQ * binormal;
            tangent = newTangent;
            position = newPosition;

        }

        // Transport, hinting the normal.
        public void Transport(Vector3 newPosition, Vector3 newTangent, Vector3 newNormal, float twist)
        {
            normal = Quaternion.AngleAxis(twist, newTangent) * newNormal;
            tangent = newTangent;
            binormal = Vector3.Cross(normal, tangent);
            position = newPosition;
        }

        public Matrix4x4 ToMatrix(Axis mainAxis)
        {
            Matrix4x4 basis = new Matrix4x4();

            int xo = ((int)mainAxis) % 3 * 4;
            int yo = ((int)mainAxis + 1) % 3 * 4;
            int zo = ((int)mainAxis + 2) % 3 * 4;

            basis[xo]     = tangent[0];
            basis[xo + 1] = tangent[1];
            basis[xo + 2] = tangent[2];

            basis[yo]     = binormal[0];
            basis[yo + 1] = binormal[1];
            basis[yo + 2] = binormal[2];

            basis[zo]     = normal[0];
            basis[zo + 1] = normal[1];
            basis[zo + 2] = normal[2];

            return basis;
        }

        public void DebugDraw(float size)
        {
            Debug.DrawRay(position, binormal * size, Color.red);
            Debug.DrawRay(position, normal * size, Color.green);
            Debug.DrawRay(position, tangent * size, Color.blue);
        }
	}
}

